package cn.tworice.auth.start;

import cn.tworice.auth.constant.EasyAuthConst;
import cn.tworice.auth.error.EasyAuthError;
import cn.tworice.auth.exception.AuthException;
import cn.tworice.auth.util.KeySaving;
import cn.tworice.common.util.datatime.DateUtil;
import cn.tworice.common.util.file.FileUtils;
import cn.tworice.common.util.NetworkUtil;
import cn.tworice.common.util.cryption.RSAUtils;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.*;
import java.net.*;
import java.util.Date;

/**
 * 核验系统是否有效
 * @author 二饭 [2023/6/16]
 **/
@WebListener
@Component
@Slf4j
public class EasyAuthStartAuth implements ServletContextListener {

    /**
     * 当前应用的Key
     **/
    @Value("${tworice.easy.key}")
    private String key;

    /**
     * 核验接口地址
     **/
    @Value("${tworice.easy.authUrl}")
    private String authUrl;

    private final RestTemplate restTemplate = new RestTemplate();

    private String userCacheDir;

//    private byte[] privateKey;


    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("校验系统");
        try{
            /*
             * 获取本地缓存文件读取其中的token，并核验是否有效
             * 如果无效，则重新请求核验
             * 如果为空，则需要请求核验
             **/
            this.userCacheDir = FileUtils.getUserCacheDir() +File.separator+ EasyAuthConst.mark;
            String token = this.getFilePathContent(this.userCacheDir+File.separator+"token");
            log.info("读取本地缓存Token：{}",token);
            if(token==null){
                // 携带当前应用的key和Mac地址核验是否合法
                token = this.verifyKey(this.userCacheDir);
            }
            // 判断Token是否合法
            if(!this.verifyToken(token)){
                token = this.verifyKey(this.userCacheDir);
                if(!this.verifyToken(token)){
                    throw new AuthException("校验失败");
                }
            }
        } catch (Exception e) {
            log.error("校验失败");
            throw new EasyAuthError(e.getMessage());
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 在应用程序关闭时执行清理操作
        System.out.println("关闭Tomcat容器");
        // 执行清理逻辑
    }

    /**
     * 核验Token是否合法
     * @param token token
     * @return java.lang.String
     * @author 二饭 [2023/6/20]
     **/
    private boolean verifyToken(String token){
        if(token.isEmpty()){
            return false;
        }

        try {
            String privateKey = this.getPrivateKey();

            token = RSAUtils.decrypt(RSAUtils.stringToBytes(token), privateKey);
            // 解密Token查看是否合法
            Algorithm algorithm = Algorithm.HMAC256(key);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT decodedJWT = verifier.verify(token);

            return this.verifyTime(decodedJWT) && this.verifyMac(decodedJWT) && this.verifyKey(decodedJWT);
        }catch (Exception exception){
            log.error(exception.getMessage());
            return false;
        }
    }

    /**
     * 校验Token有效时间
     * @param decodedJWT Token
     * @return boolean
     */
    private boolean verifyTime(DecodedJWT decodedJWT) {
        long expirationDate = decodedJWT.getClaim("exp").asLong();
        log.info("系统有效期至：{}", DateUtil.formatDate(expirationDate));
        return expirationDate >= new Date().getTime();
    }

    private boolean verifyMac(DecodedJWT decodedJWT) throws AuthException {
        String macAddr = NetworkUtil.getWlanMAC();
        log.info("本机Mac地址：{}", macAddr);
        String tokenMac = decodedJWT.getClaim("mac").asString();
        log.info("Token Mac地址：{}", tokenMac);
        return macAddr.equals(tokenMac);
    }

    private boolean verifyKey(DecodedJWT decodedJWT) {
        log.info("系统Key：{}", this.key);
        String id = decodedJWT.getClaim("id").asString();
        log.info("Token Key:{}",id);
        return this.key.equals(id);
    }

    /**
     * 获取本地存储的私钥
     * @return java.lang.String
     * @author 二饭 [2023/6/23]
     **/
    private String getPrivateKey(){
        return this.getFilePathContent(this.userCacheDir + File.separator + KeySaving.PRIVATE_KEY_FILE);
    }

    /**
     * 访问服务端效验Key是否合法
     * @param userCacheDir 效验结果要存储的路径
     * @return java.lang.String
     * @author 二饭 [2023/6/20]
     **/
    private String verifyKey(String userCacheDir) throws AuthException {
        String macAddr = NetworkUtil.getWlanMAC();
        String url = this.authUrl + "?key=" + this.key + "&mac=" + macAddr;

        log.info("校验地址：{}",url);
        HttpMethod method = HttpMethod.GET;
        ResponseEntity<String> response = restTemplate.exchange(url, method, null, String.class);
        String responseBody = response.getBody();
        JSONObject result = JSONObject.parseObject(responseBody);

        /**
         * 对请求结果进行解析
         * {
         *     status:{
         *          code:200,
         *          message:'token'
         *     }
         * }
         */
        JSONObject status = result.getJSONObject("status");
        if(status.getInteger("code")==200){
            String token = status.getString("message");
            this.printContent(userCacheDir+File.separator+"token", token);
            this.printContent(userCacheDir+File.separator+"private.key", result.getJSONObject("data").getString("privateKey"));
            return token;
        }else{
            throw new AuthException(status.getString("message"));
        }
    }

    /**
     * 获取Mac地址
     * @return java.lang.String
     * @author 二饭 [2023/6/20]
     **/
    private String getMacAddr() throws AuthException {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);

            if (networkInterface != null) {
                byte[] macAddressBytes = networkInterface.getHardwareAddress();

                if (macAddressBytes != null) {
                    StringBuilder macAddressBuilder = new StringBuilder();

                    for (byte b : macAddressBytes) {
                        macAddressBuilder.append(String.format("%02X:", b));
                    }

                    String macAddress = macAddressBuilder.toString();
                    return macAddress.substring(0, macAddress.length() - 1);
                } else {
                    throw new AuthException("无法获取MAC地址");
                }
            } else {
                throw new AuthException("无法获取网络接口");
            }
        } catch (UnknownHostException | SocketException e) {
            throw new AuthException("获取MAC地址时发生错误: " + e.getMessage());
        }
    }

    /**
     * 获取指定路径文件内容
     * @param path 路径
     * @return java.lang.String
     * @author 二饭 [2023/6/20]
     **/
    private String getFilePathContent(String path){
        File file = new File(path);

        if (!file.exists()) {
            // 创建文件所在的文件夹（如果不存在）
            File parentDir = file.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }
            // 创建文件
            try {
                file.createNewFile();
                return null;
            } catch (IOException e) {
                System.out.println("文件创建失败: " + e.getMessage());
            }
        } else {
            try (FileInputStream fis = new FileInputStream(path);
                 InputStreamReader isr = new InputStreamReader(fis);
                 BufferedReader br = new BufferedReader(isr)) {
                return br.readLine();
            } catch (IOException e) {
                System.out.println("读取文件时发生错误: " + e.getMessage());
            }
        }
        return null;
    }

    /**
     * 将内容写入文件
     *
     * @param path 文件路径
     * @author 二饭 [2023/6/20]
     **/
    private void printContent(String path, String content) throws AuthException {
        try {
            File file = new File(path);
            file.getParentFile().mkdirs();
            FileWriter fw = new FileWriter(file);
            PrintWriter writer = new PrintWriter(fw);
            writer.println(content);
            writer.close();
        } catch (IOException e) {
            throw new AuthException(e.getMessage());
        }
    }

}